Skip to content

fix(web): defer command palette filesystem navigation - #2109

Merged
juliusmarminge merged 2 commits into
mainfrom
t3code/hover-permission-prefetch
Jul 28, 2026
Merged

fix(web): defer command palette filesystem navigation#2109
juliusmarminge merged 2 commits into
mainfrom
t3code/hover-permission-prefetch

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Apr 17, 2026

Copy link
Copy Markdown
Member

Problem

Command palette filesystem navigation committed the next path before its browse result was available. Moving into a directory, moving up, or opening the initial add-project directory could briefly replace the current results with an empty list.

Fix

  • Load the destination through the current environment-scoped Effect atom query before committing palette state.
  • Await asynchronous browse item actions so command execution stays aligned with the navigation.
  • Use latest-intent coordination so overlapping navigation or newer query input cannot be overwritten by stale preload completion.
  • Skip the preload wait when an environment is not connected so offline project pickers transition immediately.
  • Remove the obsolete highlighted-row lookup from browse filtering.
  • Preserve the later macOS TCC behavior from fix(desktop): stop looping macOS TCC permission prompts #2745: child directories are read only after an explicit navigation action, never from hover or exact-name typing.
  • Cover the async action boundary with a focused unit test.

This is the current-architecture port of the original intent behind this PR after the React Query-to-Effect atom migration.

Surfaces

  • Web and desktop share this command palette and receive the fix.
  • Mobile does not use this command palette flow.
  • Local and remote environments use the same environment-scoped browse atom path.

Testing

  • vp test run src/components/CommandPalette.logic.test.ts --project unit from apps/web — 9 passed
  • vp run --filter @t3tools/web typecheck
  • vp lint --report-unused-disable-directives apps/web/src/components/CommandPalette.tsx apps/web/src/components/CommandPalette.logic.ts apps/web/src/components/CommandPalette.logic.test.ts — passes with two pre-existing nested-component warnings
  • vp fmt --check apps/web/src/components/CommandPalette.tsx apps/web/src/components/CommandPalette.logic.ts apps/web/src/components/CommandPalette.logic.test.ts
  • React Doctor on the three changed files — 92/100, one existing command-palette state-organization warning

Implemented with GPT-5.6 in the Codex harness via T3 Code.


Note

Low Risk
UI/navigation timing change in the shared web/desktop command palette; no auth or data-model changes, with focused unit tests for the new coordinator behavior.

Overview
Fixes a flash of empty directory listings when navigating folders in the command palette by prefetching browse results before updating path state.

browseTo, browseUp, and opening the add-project local-folder flow now run through a BrowseNavigationCoordinator: call the environment-scoped filesystem browse query first, then commit query/view changes only if that navigation is still current. Overlapping navigations or query/view changes call invalidate() so stale loads never commit.

canPreloadBrowsePath skips prefetch when the environment is not connected. Browse item actions await async navigation handlers. filterBrowseEntries no longer takes highlight state or returns highlightedEntry.

Unit tests cover async browse actions, coordinator ordering/invalidation, and preload gating.

Reviewed by Cursor Bugbot for commit 192da77. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Defer command palette filesystem navigation until directory listings are prefetched

  • Introduces createBrowseNavigationCoordinator to serialize in-flight navigations so only the latest navigation commits UI state; earlier or invalidated navigations are discarded.
  • Browse actions (browseTo, browseUp) now prefetch directory listings before committing view updates, preventing stale or out-of-order UI states.
  • Adds canPreloadBrowsePath to gate prefetching on environment connection phase ('connected' only).
  • Navigation is invalidated on unmount, view push/pop, and search query changes to prevent stale navigations from committing.
  • Behavioral Change: browse action promises now resolve only after navigation completes, and filterBrowseEntries no longer returns highlightedEntry.

Macroscope summarized 192da77.

@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 80569782-6d98-4dd0-b365-dd89fca17764

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/hover-permission-prefetch

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Apr 17, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Stale highlightedItemValue dependency causes unnecessary recomputation
    • Removed the unused highlightedItemValue parameter from filterBrowseEntries, deleted the dead highlightedEntry computation inside the function, and dropped highlightedItemValue from the useMemo dependency array to prevent unnecessary recomputation on hover.

Create PR

Or push these changes by commenting:

@cursor push 689afc5e89
Preview (689afc5e89)
diff --git a/apps/web/src/components/CommandPalette.logic.ts b/apps/web/src/components/CommandPalette.logic.ts
--- a/apps/web/src/components/CommandPalette.logic.ts
+++ b/apps/web/src/components/CommandPalette.logic.ts
@@ -51,10 +51,8 @@
 export function filterBrowseEntries(input: {
   browseEntries: ReadonlyArray<FilesystemBrowseEntry>;
   browseFilterQuery: string;
-  highlightedItemValue: string | null;
 }): {
   filteredEntries: FilesystemBrowseEntry[];
-  highlightedEntry: FilesystemBrowseEntry | null;
   exactEntry: FilesystemBrowseEntry | null;
 } {
   const lowerFilter = input.browseFilterQuery.toLowerCase();
@@ -66,18 +64,12 @@
       (showHidden || !entry.name.startsWith(".")),
   );
 
-  let highlightedEntry: FilesystemBrowseEntry | null = null;
-  if (input.highlightedItemValue?.startsWith("browse:")) {
-    const highlightedPath = input.highlightedItemValue.slice("browse:".length);
-    highlightedEntry = filteredEntries.find((entry) => entry.fullPath === highlightedPath) ?? null;
-  }
-
   const exactEntry =
     input.browseFilterQuery.length > 0
       ? (filteredEntries.find((entry) => entry.name === input.browseFilterQuery) ?? null)
       : null;
 
-  return { filteredEntries, highlightedEntry, exactEntry };
+  return { filteredEntries, exactEntry };
 }
 
 export function getBrowsePrefetchPaths(input: {

diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx
--- a/apps/web/src/components/CommandPalette.tsx
+++ b/apps/web/src/components/CommandPalette.tsx
@@ -369,8 +369,8 @@
   });
   const browseEntries = browseResult?.entries ?? EMPTY_BROWSE_ENTRIES;
   const { filteredEntries: filteredBrowseEntries, exactEntry: exactBrowseEntry } = useMemo(
-    () => filterBrowseEntries({ browseEntries, browseFilterQuery, highlightedItemValue }),
-    [browseEntries, browseFilterQuery, highlightedItemValue],
+    () => filterBrowseEntries({ browseEntries, browseFilterQuery }),
+    [browseEntries, browseFilterQuery],
   );
 
   const prefetchBrowsePath = useCallback(

You can send follow-ups to the cloud agent here.

Comment thread apps/web/src/components/CommandPalette.tsx Outdated
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Apr 17, 2026
@macroscopeapp

macroscopeapp Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved

This PR adds async coordination and prefetching to command palette filesystem navigation, improving responsiveness and handling race conditions. The changes are well-tested, self-contained to the UI component, and don't alter fundamental browsing behavior.

You can customize Macroscope's approvability policy. Learn more.

@macroscopeapp
macroscopeapp Bot dismissed their stale review April 17, 2026 05:32

Dismissing prior approval to re-evaluate 11192b6

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Apr 17, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Tests verify unreachable state combinations for prefetch paths
    • Fixed the tests to use reachable state combinations: trailing-separator query with canBrowseUp:true/exactEntry:null for parent prefetch, non-trailing query with canBrowseUp:false/exactEntry:non-null for child prefetch, and partial query with both false/null for empty result.

Create PR

Or push these changes by commenting:

@cursor push f6ccddbfd3
Preview (f6ccddbfd3)
diff --git a/apps/web/src/components/CommandPalette.logic.test.ts b/apps/web/src/components/CommandPalette.logic.test.ts
--- a/apps/web/src/components/CommandPalette.logic.test.ts
+++ b/apps/web/src/components/CommandPalette.logic.test.ts
@@ -167,26 +167,36 @@
 });
 
 describe("getBrowsePrefetchPaths", () => {
-  it("prefetches the parent path and an exact typed child path", () => {
+  it("prefetches child path when query has no trailing separator and an exact entry matches", () => {
     expect(
       getBrowsePrefetchPaths({
         browseQuery: "~/Downloads",
-        canBrowseUp: true,
+        canBrowseUp: false,
         exactEntry: {
           name: "Downloads",
           fullPath: "/Users/test/Downloads",
         },
       }),
-    ).toEqual(["~/", "~/Downloads/"]);
+    ).toEqual(["~/Downloads/"]);
   });
 
-  it("does not prefetch a child path when the user only has a highlighted browse row", () => {
+  it("prefetches parent path when query has a trailing separator", () => {
     expect(
       getBrowsePrefetchPaths({
-        browseQuery: "~/Do",
+        browseQuery: "~/Downloads/",
         canBrowseUp: true,
         exactEntry: null,
       }),
     ).toEqual(["~/"]);
   });
+
+  it("returns no paths for a partial query with no exact match", () => {
+    expect(
+      getBrowsePrefetchPaths({
+        browseQuery: "~/Do",
+        canBrowseUp: false,
+        exactEntry: null,
+      }),
+    ).toEqual([]);
+  });
 });

You can send follow-ups to the cloud agent here.

Comment thread apps/web/src/components/CommandPalette.logic.test.ts
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Apr 17, 2026
@juliusmarminge

Copy link
Copy Markdown
Member Author

Closing as superseded by the newer macOS desktop permission/TCC work, including #2745.

Load each filesystem browse destination into the environment atom cache before committing command palette state so directory changes do not flash an empty result set. Preserve the later TCC behavior by only loading child directories after an explicit navigation action.

Co-authored-by: codex <codex@users.noreply.github.com>
@juliusmarminge juliusmarminge changed the title Prefetch exact browse child paths in the command palette fix(web): defer command palette filesystem navigation Jul 28, 2026
@juliusmarminge
juliusmarminge force-pushed the t3code/hover-permission-prefetch branch from 11192b6 to c1b118a Compare July 28, 2026 22:27
@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Jul 28, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit c1b118a. Configure here.

Comment thread apps/web/src/components/CommandPalette.tsx Outdated
Comment thread apps/web/src/components/CommandPalette.tsx
Make overlapping command palette browse actions commit only the latest intent, and invalidate pending navigation when the user changes the query or view. Skip the preload wait for environments that are not connected so offline project pickers remain responsive.

Co-authored-by: codex <codex@users.noreply.github.com>
@macroscopeapp
macroscopeapp Bot dismissed their stale review July 28, 2026 22:45

Dismissing prior approval to re-evaluate 192da77

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Jul 28, 2026
@juliusmarminge
juliusmarminge merged commit 1ba3d01 into main Jul 28, 2026
17 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/hover-permission-prefetch branch July 28, 2026 22:58
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Jul 29, 2026
## What's Changed
* Add OTA update checks to mobile settings by @juliusmarminge in pingdotgg/t3code#4686
* fix(mobile): threads load snapped to bottom on iOS by @t3dotgg in pingdotgg/t3code#4689
* feat: default sidebar v2 on for nightly and dev builds by @t3dotgg in pingdotgg/t3code#4491
* fix(web): 33 web UI fixes by @maxktz in pingdotgg/t3code#4700
* Make mobile Thread List v2 the default by @juliusmarminge in pingdotgg/t3code#4717
* Fix mobile showcase workflow without Clerk by @juliusmarminge in pingdotgg/t3code#4718
* Fix relay credential lookup for unlinked environments by @juliusmarminge in pingdotgg/t3code#4692
* Settle merged PR threads immediately by @t3dotgg in pingdotgg/t3code#4704
* feat(web): add maria's sidebar header artwork toggle by @maxktz in pingdotgg/t3code#4652
* feat(web): add appearance settings category by @maxktz in pingdotgg/t3code#4715
* fix(desktop): allow updater-controlled relaunch by @0x4bs3nt in pingdotgg/t3code#4721
* fix(web): prevent diff panel scroll jumping by @0x4bs3nt in pingdotgg/t3code#4724
* Link inline code file paths in chat markdown by @juliusmarminge in pingdotgg/t3code#4726
* Fix Android showcase capture and rebuild v2 queued rows by @juliusmarminge in pingdotgg/t3code#4730
* perf(server): negotiate permessage-deflate on the websocket by @t3dotgg in pingdotgg/t3code#4705
* docs: overhaul agent guidance by @t3dotgg in pingdotgg/t3code#4782
* Prevent sidebar row labels from truncating by @juliusmarminge in pingdotgg/t3code#4789
* perf(server): gzip large thread snapshots by @t3dotgg in pingdotgg/t3code#4788
* fix(web): stashed prompts now survive switching providers by @t3dotgg in pingdotgg/t3code#4787
* Fix Git ref refresh resource storms by @juliusmarminge in pingdotgg/t3code#4727
* perf(server): trim stale context-window rows and drop dead replay RPC by @t3dotgg in pingdotgg/t3code#4791
* fix(web): defer command palette filesystem navigation by @juliusmarminge in pingdotgg/t3code#2109
* fix(release): skip scripts during Vercel installs by @t3dotgg in pingdotgg/t3code#4796
* refactor(client): share filesystem browse navigation by @juliusmarminge in pingdotgg/t3code#4797
* fix(mobile): defer filesystem navigation by @juliusmarminge in pingdotgg/t3code#4799
* refactor(server): use native HTTP compression streams by @juliusmarminge in pingdotgg/t3code#4798
* Remove Connect waitlist and add GA announcement tooling by @juliusmarminge in pingdotgg/t3code#4691


**Full Changelog**: pingdotgg/t3code@v0.0.29...v0.0.30

## What's Changed
* Add OTA update checks to mobile settings by @juliusmarminge in pingdotgg/t3code#4686
* fix(mobile): threads load snapped to bottom on iOS by @t3dotgg in pingdotgg/t3code#4689
* feat: default sidebar v2 on for nightly and dev builds by @t3dotgg in pingdotgg/t3code#4491
* fix(web): 33 web UI fixes by @maxktz in pingdotgg/t3code#4700
* Make mobile Thread List v2 the default by @juliusmarminge in pingdotgg/t3code#4717
* Fix mobile showcase workflow without Clerk by @juliusmarminge in pingdotgg/t3code#4718
* Fix relay credential lookup for unlinked environments by @juliusmarminge in pingdotgg/t3code#4692
* Settle merged PR threads immediately by @t3dotgg in pingdotgg/t3code#4704
* feat(web): add maria's sidebar header artwork toggle by @maxktz in pingdotgg/t3code#4652
* feat(web): add appearance settings category by @maxktz in pingdotgg/t3code#4715
* fix(desktop): allow updater-controlled relaunch by @0x4bs3nt in pingdotgg/t3code#4721
* fix(web): prevent diff panel scroll jumping by @0x4bs3nt in pingdotgg/t3code#4724
* Link inline code file paths in chat markdown by @juliusmarminge in pingdotgg/t3code#4726
* Fix Android showcase capture and rebuild v2 queued rows by @juliusmarminge in pingdotgg/t3code#4730
* perf(server): negotiate permessage-deflate on the websocket by @t3dotgg in pingdotgg/t3code#4705
* docs: overhaul agent guidance by @t3dotgg in pingdotgg/t3code#4782
* Prevent sidebar row labels from truncating by @juliusmarminge in pingdotgg/t3code#4789
* perf(server): gzip large thread snapshots by @t3dotgg in pingdotgg/t3code#4788
* fix(web): stashed prompts now survive switching providers by @t3dotgg in pingdotgg/t3code#4787
* Fix Git ref refresh resource storms by @juliusmarminge in pingdotgg/t3code#4727
* perf(server): trim stale context-window rows and drop dead replay RPC by @t3dotgg in pingdotgg/t3code#4791
* fix(web): defer command palette filesystem navigation by @juliusmarminge in pingdotgg/t3code#2109
* fix(release): skip scripts during Vercel installs by @t3dotgg in pingdotgg/t3code#4796
* refactor(client): share filesystem browse navigation by @juliusmarminge in pingdotgg/t3code#4797
* fix(mobile): defer filesystem navigation by @juliusmarminge in pingdotgg/t3code#4799
* refactor(server): use native HTTP compression streams by @juliusmarminge in pingdotgg/t3code#4798
* Remove Connect waitlist and add GA announcement tooling by @juliusmarminge in pingdotgg/t3code#4691


**Full Changelog**: pingdotgg/t3code@v0.0.29...v0.0.30

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant